#include "alu.h"

// the HI register for multiplication overflow
static word hi;

word alu (word a, word b, word aluop)
{
	sword A = a, B = b;
	word r;
	switch (aluop)
	{
		case NEG:
			r = alu_neg (a);
			break;
		case ADD:
			r = alu_add (a, b);
			break;
		case SUB:
			r = alu_sub (a, b);
			break;
		case MUL:
			r = alu_mul (a, b);
			break;
		case EXT:
			if (a & (1 << 31))
			{
				hi = 0xFFFFFFFF;
			}
			else
			{
				hi = 0;
			}
			break;
		case LDHI:
			r = hi;
			break;
		case STHI:
			hi = a;
			break;
		case DIV:
			r = alu_div (a, b);
			break;
		case MOD:
			r = alu_mod (a, b);
			break;
		case ABS:
			r = alu_abs (a);
			break;
		case AND:
			r = a & b;
			break;
		case OR:
			r = a | b;
			break;
		case XOR:
			r = a ^ b;
			break;
		case NOT:
			r = ~ a;
			break;
		case SLL:
			r = a << b;
			break;
		case SRL:
			r = a >> b;
			break;
		case SLA:
			r = (word) (A << B);
			break;
		case SRA:
			r = (word) (A >> B);
		case LT:
			r = (A < B) ? ~ 0 : 0;
			break;
		case GT:
			r = (A > B) ? ~ 0 : 0;
			break;
		case EQ:
			r = (A == B) ? ~ 0 : 0;
			break;
		case NE: 
			r = (A != B) ? ~ 0 : 0;
			break;
		case LE:
			r = (A <= B) ? ~ 0 : 0;
			break;
		case GE:
			r = (A >= B) ? ~ 0 : 0;
		default:
			r = 0;
	}
	return r;
}

// a + b;
static word alu_add (word a, word b)
{
	word r, x, n;
	if (a == 0) return b;
	if (b == 0) return a;
	x = a ^ b;
	n = (a & b) << 1;
	r = alu_add (x, n);
	return r;
}

// a - b
static word alu_sub (word a, word b)
{
	word r;
	word n;
	n = alu_neg (b);
	r = alu_add (a, n);
	return r;
}

// - a
static word alu_neg (word a)
{
	word r;
	r = ~ a;
	r = alu_add (r, 1);
	return r;
}

#ifdef DEBUG
char buf [128];
#endif


// a * b 
static word alu_mul (word a, word b)
{
	word lo;
	word s;
	dword p, z, n;
	s = (a & (1 << 31)) ^ (b & (1 << 31)); // sign of the product
	a = alu_abs (a);
	b = alu_abs (b);
	z = a; 
	n = b;
	p = z * n;
	if (s)
	{
		p = dword_neg (p);
	}
	lo = (word) (p & 0xFFFFFFFF);
	hi = (word) ((p >> 32) & 0xFFFFFFFF);
#ifdef DEBUG
	sprintf (buf, "\n%08x * %08x = %08x %08x \n", a, b, hi, lo);
	tty_puts (buf);
#endif
	return lo;
}

// a / b 
static word alu_div (word a, word b) 
{
	word lo;
	dword q, n;
	word s;
	if (! b)
	{
		return ~ 0;
	}
	s = (hi & (1 << 31)) ^ (b & (1 << 31)); // sign of the quotient
#ifdef DEBUG
	sprintf (buf, "\n%08x %08x / %08x = ", hi, a, b);
	tty_puts (buf);
#endif
	q = hi;
	q = q << 32;
	q = q | a;
	q = dword_abs (q);
	b = alu_abs (b);
	n = b;
	q = q / n;  
	if (s)
	{
		q = dword_neg (q);
	}
	lo = (word) (q & 0xFFFFFFFF);
	hi = (word) ((q >> 32) & 0xFFFFFFFF);
#ifdef DEBUG
	sprintf (buf, "%08x %08x \n", hi, lo);
	tty_puts (buf);
#endif
	return lo;
}

static word alu_mod (word a, word b)
{
	word lo;
	dword z, n;
	word r, s;
	if (! b)
	{
		return ~ 0;
	}
	z = hi;
	z = z << 32;
	z = z | a;
	z = dword_abs (z);
	b = alu_abs (b);
	n = b;
	z = z % n;  
	r = (word) (z & 0xFFFFFFFF);
	hi = 0;
	return r;
}

static word alu_abs (word a)
{
	if (a & (1 << 31))
	{
		return alu_neg (a);
	}
	return a;
}

static dword dword_abs (dword a)
{
	if (a & ((dword) 1 << 63))
	{
		return dword_neg (a);
	}
	return a;
}

static dword dword_neg (dword a)
{
	dword r, n;
	n = ~ a;
	r = dword_add (n, (dword) 1);
	return r;
}

// a + b;
static dword dword_add (dword a, dword b)
{
	dword r, x, n;
	if (a == 0) return b;
	if (b == 0) return a;
	x = a ^ b;
	n = (a & b) << 1;
	r = dword_add (x, n);
	return r;
}

// a - b
static dword dword_sub (dword a, dword b)
{
	dword r, n;
	r = a;
	n = dword_neg (b);
	r = dword_add (r, n);
	return r;
}


